//
//  scanner.c
//  Lab3
//
//  Created by Bryce Holton.
//  Copyright (c) 2014 Bryce Holton. All rights reserved.
//
// Josh Owens Josh Averett Hussein Amr

#include <stdio.h>
#include "scanner.h"
#include <malloc.h>
#include <stdlib.h>

/*******************
 Static functions needed for the scanner
 You need to design the proper parameter list and 
 return types for functions with ???.
 ******************/
static char get_char(char** str);
static void skip_comment(char** str);
static void skip_blanks(char** str);
static Token* get_word(char** str);
static Token* get_number(char** str);
static Token* get_string(char** str);
static Token* get_special(char** str);
static void downshift_word(Token* in);
static BOOLEAN is_reserved_word(Token* in);

typedef enum
{
    LETTER, DIGIT, QUOTE, SPECIAL, EOF_CODE, SPACE
}
CharCode;

/*********************
 Static Variables for Scanner
 Must be initialized in the init_scanner function.
 *********************/
static FILE *src_file;
static char src_name[MAX_FILE_NAME_LENGTH];
static char todays_date[DATE_STRING_LENGTH];
static CharCode char_table[256];  // The character table
char current_line[MAX_SOURCE_LINE_LENGTH];
char* indexer = current_line;

typedef struct
{
    char *string;
    TokenCode token_code;
}
RwStruct;

const RwStruct rw_table[] = {
    {"do",DO},{"if",IF},{"in",IN},{"of",OF},{"or",OR},{"to",TO}, //Reserved words of size 2
    {"and",AND},{"div",DIV},{"end",END},{"for",FOR},{"mod",MOD},{"nil",NIL},{"not",NOT},{"set",SET},{"var",VAR}, //Reserved words of size 3
    {"case",CASE},{"else",ELSE},{"file",FFILE},{"goto",GOTO},{"then",THEN},{"type",TYPE},{"with",WITH}, //Reserved words of size 4
    {"array",ARRAY},{"begin",BEGIN},{"const",CONST},{"label",LABEL},{"until",UNTIL},{"while",WHILE},  //Reserved words of size 5
    {"downto",DOWNTO}, {"packed",PACKED},{"record",RECORD}, {"repeat",REPEAT},  // Reserved words of size 6
    {"program", PROGRAM}, // Reserved words of size 7
    {"function", FUNCTION}, // Reserved words of size 8
    {"procedure", PROCEDURE}  // Reserved words of size 9
};

const RwStruct special_table[21] = {
	{"^",UPARROW},{"*",STAR},{"(",LPAREN},{")",RPAREN},{"-",MINUS},{"+",PLUS},{"=",EQUAL},{"[",RBRACKET},{"]",LBRACKET},{":",COLON},{";",SEMICOLON},
	{"<",LT},{">",GT},{",",COMMA},{".",PERIOD},{"/",SLASH},{":=",COLONEQUAL},{"<=",LE},{">=",GE},{"<>",NE},{"..",DOTDOT}
};

void init_scanner(FILE *source_file, char source_name[], char date[])
{
	unsigned char c;
    src_file = source_file;
    strcpy(src_name, source_name);
    strcpy(todays_date, date);
    
    for(c = 0; c < 128; ++c)
	{
		if(c == '\'') char_table[c] = QUOTE;
		else if(c >= '0' && c <= '9') char_table[c] = DIGIT;
		else if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) char_table[c] = LETTER;
		else if(c == '^' || c == '*' || c == '(' || c == ')' ||
			    c == '-' || c == '+' || c == '=' || c == '[' ||
				c == ']' || c == ':' || c == ';' || c == '<' ||
				c == '>' || c == ',' || c == '/') char_table[c] = SPECIAL;
		else if(c == '.') char_table[c] = EOF_CODE;
		else char_table[c] = SPACE;
	}
}
BOOLEAN get_source_line()
{
    char print_buffer[MAX_SOURCE_LINE_LENGTH + 9];
//    char source_buffer[MAX_SOURCE_LINE_LENGTH];  //I've moved this to a function parameter.  Why did I do that?
    static int line_number = 0;
	indexer = current_line;
    
	if (fgets(current_line, MAX_SOURCE_LINE_LENGTH, src_file) != NULL)
    {
        ++line_number;
		sprintf(print_buffer, "%4d: %s", line_number, current_line);
        print_line(print_buffer, src_name, todays_date);
        return (TRUE);
    }
    else
    {
        return (FALSE);
    }
}
Token* get_token()
{
	skip_blanks(&indexer);

	switch(char_table[*indexer])
	{
	case LETTER:
		return get_word(&indexer);
	case DIGIT:
		return get_number(&indexer);
	case QUOTE:
		return get_string(&indexer);
	case SPECIAL:
		return get_special(&indexer);
	default:
		return NULL;
	}
}
static char get_char(char** str)
{
    /*
     If at the end of the current line (how do you check for that?),
     we should call get source line.  If at the EOF (end of file) we should
     set the character ch to EOF and leave the function.
     */
    
    /*
     Write some code to set the character ch to the next character in the buffer
     */

	char ch = ' ';
	while(char_table[ch] == SPACE || ch == '{')
	{
		skip_blanks(str);
		skip_comment(str);
		ch = **str;
	}

	return ch;
}
static void skip_blanks(char** str)
{
	if(!**str || **str == '\n') get_source_line();
	if(char_table[**str] != SPACE) return;
    while(char_table[**str] == SPACE)
	{
		++*str;
		if(**str == '\0') get_source_line();
	}
}
static void skip_comment(char** str)
{
	int depth = 1;
    /*
     Write some code to skip past the comments in the program and return a pointer
     to the first non blank character.  Watch out for the EOF character.
     */
	if(!**str || **str == '\n') get_source_line();
	if(**str != '{') return;

	while(depth)
	{
		++*str;
		if(**str == '\0') get_source_line();
		else if(**str == '{') ++depth;
		else if(**str == '}') --depth;
	}
}
static Token* get_word(char** str)
{
	char* start = *str;
	Token* token = (Token*)malloc(sizeof(Token));
	int length;

	token->literal_type = STRING_LIT;
	token->token_code = IDENTIFIER;
	token->next = NULL;
    /*
     Write some code to Extract the word
     */
	while(char_table[**str] == LETTER || char_table[**str] == DIGIT)
	{
		++*str;
	}

	length = (int)*str - (int)start;
	token->literal_value.string_lit = (char*)malloc(length + 1);
	strncpy(token->literal_value.string_lit, start, length);
	token->literal_value.string_lit[length] = '\0';
    
    //Downshift the word, to make it lower case
	downshift_word(token);
    
    /*
     Write some code to Check if the word is a reserved word.
     if it is not a reserved word its an identifier.
     */

	is_reserved_word(token);

	return token;
}
static Token* get_number(char** str)
{
    /*
     Write some code to Extract the number and convert it to a literal number.
     */
	char* start = *str;
	int length;
	Token* token = (Token*)malloc(sizeof(Token));
	token->literal_type = INTEGER_LIT;
	token->token_code = NUMBER;
	token->next = NULL;

	while(char_table[**str] == DIGIT || **str == '.'|| **str == 'e' || **str == '-')
	{
		if(**str == '.' || **str == 'e') token->literal_type = REAL_LIT;
		++*str;
	}
	
	if(token->literal_type == INTEGER_LIT) token->literal_value.integer_lit = atoi(start);
	else token->literal_value.real_lit = atof(start);
	return token;
}
static Token* get_string(char** str)
{
    /*
     Write some code to Extract the string
     */
	char* start = ++*str; // Skip the opening '
	int length;
	Token* token = (Token*)malloc(sizeof(Token));
	token->literal_type = STRING_LIT;
	token->token_code = STRING;
	token->next = NULL;
	while(**str != '\'') ++*str;
	length = (int)*str - (int)start;
	token->literal_value.string_lit = (char*)malloc(length + 1);
	strncpy(token->literal_value.string_lit, start, length - 1);
	token->literal_value.string_lit[length] = '\0';
	++*str; // Skip the last '
	return token;
}
static Token* get_special(char** str)
{
    /*
     Write some code to Extract the special token.  Most are single-character
     some are double-character.  Set the token appropriately.
     */
	int i;
	Token* token = (Token*)malloc(sizeof(Token));
	token->literal_type = STRING_LIT;
	token->next = NULL;

	for(i = 0; i < 21; ++i)
	{
		if(**str == special_table[i].string[0])
		{
			if((*str)[1] == special_table[i].string[1] || special_table[i].string[1] == '\0')
			{
				token->literal_value.string_lit = special_table[i].string;
				token->token_code = special_table[i].token_code;
				*str += strlen(special_table[i].string);
				return token;
			}
		}
	}
	return NULL;
}
static void downshift_word(Token* in)
{
  int i;
	char* str = in->literal_value.string_lit;
   for(i = 0; str[i]; ++i)
   {
     str[i] = (char)tolower(str[i]);
   }
}
static BOOLEAN is_reserved_word(Token* in)
{
    /*
     Examine the reserved word table and determine if the function input is a reserved word.
     */
	int i;
	for(i = 0; i < 35; ++i){
		RwStruct st = rw_table[i];
		if(!strcmp(in->literal_value.string_lit, st.string))
		{
			free(in->literal_value.string_lit);
			in->literal_value.string_lit = st.string;
			in->token_code = st.token_code;
			return TRUE;
		}
	}

    return FALSE;
}
